// Learn TypeScript:
//  - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/typescript.html
//  - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/typescript.html
// Learn Attribute:
//  - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/reference/attributes.html
//  - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - [Chinese] https://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html
//  - [English] http://www.cocos2d-x.org/docs/creator/manual/en/scripting/life-cycle-callbacks.html


const { ccclass, property } = cc._decorator;
@ccclass
export default class NewClass extends cc.Component {

    @property()
    blockWidth: number = 68

    @property()
    blockHeight: number = 68

    @property({
        type: cc.Prefab,
        tooltip: '怪物'
    })
    monster: Array<cc.Prefab> = []

    @property({
        type: cc.Integer,
        tooltip: '分数（和怪物位置匹配）'
    })
    score: number[] = []

    @property()
    max: number = 5

    @property()
    min: number = 0

    @property()
    squashDuration: number = 0.1

    @property({
        type: cc.Integer,
        tooltip: '最小增量（在这个数值之后分数翻倍）'
    })
    minIncrement: number = 4

    @property({
        type: cc.AudioSource
    })
    exchangeVoice: cc.AudioSource

    @property({
        type: cc.AudioSource
    })
    popVoice_0: cc.AudioSource

    @property({
        type: cc.AudioSource
    })
    popVoice_1: cc.AudioSource
    @property({
        type: cc.AudioSource
    })
    popVoice_1_plus: cc.AudioSource

    @property({
        type: cc.AudioSource
    })
    popVoice_2: cc.AudioSource
    @property({
        type: cc.AudioSource
    })
    popVoice_2_plus: cc.AudioSource

    @property()
    comboMaxNumber = 3

    @property()
    voicePlayTime = 300

    // LIFE-CYCLE CALLBACKS:

    getPos: Function

    dontTouch = false

    total = 0 // 总分

    comboNumber = 0 // 连击次数

    get mask() {
        return <$base.Mask>this.getComponent(cc.Mask)
    }

    get graphics() {
        return this.mask._graphics
    }

    negation = {
        'left': 'right',
        'right': 'left',
        'bottom': 'top',
        'top': 'bottom'
    }

    maskList: { x: number, y: number }[] = []

    createMask() { // 创建蒙层
        var graphics = this.graphics;
        graphics.clear();
        this.maskList.forEach(({
            x, y
        }) => {
            this.graphics.rect(x - this.blockWidth / 2, y - this.blockHeight / 2, this.blockWidth, this.blockHeight)
        })
        graphics.fill()
    }

    creationMonster(monsterType?: string, never: number[] = null) { // 怪物创建
        const num = monsterType || (never
            ? $base.excludedRandomNumber(this.max, this.min, never)
            : $base.randomNum(this.max, this.min))
        const prefab = $base.createNode({
            name: this.monster[num].name
        })
        return {
            uuid: prefab.uuid,
            breed: num,
            prefab
        }
    }

    createdBlock(data): {
        info: {
            uuid: any;
            monsterType: string | number;
        }
    } { // 创建格子
        const { x, y, monsterInitType, id }: {
            x: number, y: number, monsterInitType?: string, id: number
        } = data
        let neverArray = $base._model === 'never' && !monsterInitType
            ? [
                $base.getSibling('left', id),
                $base.getSibling('lowerLeft', id),
                $base.getSibling('lowerRight', id),
                $base.getSibling('bottom', id)
            ].filter((n) => n && n.info).map((n) => Number(n.info.monsterType))
            : null
        const {
            uuid,
            breed,
            prefab
        } = this.creationMonster(monsterInitType, neverArray)
        prefab.setPosition(x, y)
        prefab.id = id

        this.node.addChild(prefab)
        this.addEvent(prefab)
        return {
            info: {
                uuid,
                monsterType: breed.toString()
            }
        }
    }

    action(node, direction, callback = () => { }) { // 根据手势移动指定的节点
        let actionTo
        const hash = {
            top: [0, this.blockHeight],
            bottom: [0, -this.blockHeight],
            left: [-this.blockWidth, 0],
            right: [this.blockWidth, 0],
        }
        actionTo = cc.moveBy(this.squashDuration, cc.v2(hash[direction][0], hash[direction][1])).easing(cc.easeCubicActionOut())
        // var squash = cc.scaleTo(this.squashDuration, 1, 0.6);
        var stretch = cc.scaleTo(this.squashDuration, 1.2, 1.2);
        var scaleBack = cc.scaleTo(this.squashDuration / 2, 1, 1);
        node.runAction(cc.sequence(stretch, actionTo, scaleBack, cc.callFunc(callback, this)));
        return this
    }

    touchLeave(e) { // 移除当前块后进行操作
        const node = e.currentTarget
        if (!node.isTouchStart || this.dontTouch) return
        const getLocation = e.getLocation()
        const id = node.id
        const {
            x,
            y
        }: {
            x: number,
            y: number
        } = getLocation
        let endX = node.startX - x
        let endY = node.startY - y
        let direction = Math.abs(endX) > Math.abs(endY)
            ? //手势向左右
            //判断向左还是向右 
            endX > 0
                ? 'left'
                : 'right'
            :  //手势向上下
            //判断手势向上还是向下
            endY > 0
                ? 'bottom'
                : 'top'
        const success = this.pawnExchange({
            direction,
            id,
            node
        })
        if(success) $base.step --
        
        node.isTouchStart = false
    }

    exchangeAni({ // 棋子交换动画
        node,
        nextNode,
        direction
    }, callback = () => { }) {
        let i = 0
        let cb = () => {
            i++
            if (i === fn.length) callback()
        }
        this.exchangeVoice.play()
        let fn = [
            this.action(node, direction, cb),
            this.action(nextNode, this.negation[direction], cb)
        ]
    }

    pawnExchange({ // 棋子交换 返回是否交换成功
        direction,
        id,
        node
    }) {
        this.dontTouch = true
        const sibling = $base.getSibling(direction, id)
        if (!sibling || !sibling.info || !sibling.info.uuid) return false
        const nextNode = <$base.Node>this.node.getChildByUuid(sibling.info.uuid)
        if (!nextNode) return false
        const mousterId = sibling.id
        const nodeId = node.id
        const dataExchange = (id, nextId) => { // 数据交换
            nextNode.id = id
            node.id = nextId
            $base.exchange(nodeId, mousterId)
        }
        dataExchange(nodeId, mousterId)// 预先进行交换
        const exchangeSuccess = $base.checkSame().same.length > 0
        this.exchangeAni({
            node,
            nextNode,
            direction
        }, () => {
            if (exchangeSuccess) {
                this.startBomb()
            } else { // 如果没有造成任何的消除那么就不交换
                dataExchange(mousterId, nodeId)
                this.exchangeAni({
                    node,
                    nextNode,
                    direction: this.negation[direction]
                }, () => {
                    this.dontTouch = false
                })
            }
        })

        return exchangeSuccess
    }

    touchStart(e) {
        if(this.dontTouch) return
        const getLocation = e.getLocation()
        const currentTarget = e.currentTarget
        currentTarget.isTouchStart = true
        currentTarget.startX = getLocation.x
        currentTarget.startY = getLocation.y
    }
    addEvent(node) { // 添加事件 进行判断手势 上下左右
        node.on(cc.Node.EventType.TOUCH_START, this.touchStart, this)
        node.on(cc.Node.EventType.MOUSE_LEAVE, this.touchLeave, this)
        node.on(cc.Node.EventType.TOUCH_CANCEL, this.touchLeave, this)
    }


    bombAni({ // 执行爆炸动画
        node, event = 'bomb' + node.name, callback
    }) {
        const anim = node.getComponent(cc.Animation);
        anim.once('finished', () => {
            anim.setCurrentTime(0, event)
            anim.stop()
            callback()

        }, this);
        anim.play(event);
    }

    startBomb() { // 开始爆炸
        this.dontTouch = true
        return this.bomb((succ) => {
            this.dontTouch = succ
            if (succ) {
                this.blockDrop(() => {// 如果填充成功 就开始下落 和补充棋子
                    this.fillBlock((succ) => { // 如果填充成功说明可以进行下一轮检查是否消除
                        if (succ) {
                            if(this.startBomb()){
                                this.comboNumber ++
                                if(this.comboNumber > this.comboMaxNumber) this.comboNumber = this.comboMaxNumber
                            }
                        }
                    })
                })
            } else {
                const voice = this[`popVoice_${this.comboNumber}_plus`]
                if(voice) {
                    setTimeout(() => {
                        voice.play()
                    }, this.voicePlayTime);
                }
                this.comboNumber = 0
            }
        })
    }

    getScore (same) { // 获取当前分数
        const totals = same.map((arr)=> {
            let diff = arr.length - (this.minIncrement - 1) // 获取增量
            let scoreList = arr.map((id) => {
                if(!$base._template[id].info) return new Error(`Monster Info Is Undefined 当前怪物信息获取不到 id 为 ${id}`)
                let s = this.score[$base._template[id].info.monsterType]
                if(!s) return new Error(`Score Is Undefined 当前怪物没有分数 id 为 ${id}`)
                return s
            })
            let total = scoreList.reduce((total, num)=> {
                return total + num
            })
            if(diff > 0) {
                total = total << diff
            }
            return total
        })
        return $base.total += totals.reduce((total, num)=> {
            return total + num
        })
    }

    set same(value) {
        this.getScore(value)
    }

    bomb(callback: Function = (boolean) => { }) { // 爆炸
        const {
            same,
            length
        } = $base.checkSame()
        const arr = []
        if (!same.length) return callback(false)
        this.same = same
        same.forEach((block) => {
            block.forEach((z) => {
                const item = $base._template[z]
                if (!item || !item.info) return arr.push(false)
                const node = <$base.Node>this.node.getChildByUuid(item.info.uuid)
                if (!node) return arr.push(false)
                this.bombAni({
                    node,
                    callback: () => {
                        const voiceName = `popVoice_${this.comboNumber}`
                        this[voiceName].play()
                        arr.push(true)
                        $base.recyclePool({
                            name: node.name,
                            node
                        })
                        delete $base._template[z].info
                        if (arr.length === length) {
                            callback(true)
                        }
                    }
                })
            })
        })
        return true
    }

    actionDrop({
        node, callback = () => { },
        number = 1,
        gap = -this.blockHeight * number
    }) { // 棋子掉落
        let actionTo
        actionTo = cc.moveBy(this.squashDuration, cc.v2(0, gap)).easing(cc.easeCubicActionOut())
        node.runAction(cc.sequence(actionTo, cc.callFunc(callback, this)))
    }

    fillBlock(callback = (boolean) => { }) { // 填充格子
        const group = [...$base.getGroup().group]
        let length = 0
        const fillBlock = group.map((g) => g.filter((item) => {
            if (!item.info) length++
            return !item.info
        })) // 拿到需要填充的分组
        let i = 0
        fillBlock.forEach((g) => {
            if (!g.length) return callback(false)
            let y = g[g.length - 1].y
            g.forEach((item) => {
                y += this.blockHeight
                const id = item.id
                const {
                    uuid,
                    breed,
                    prefab
                } = this.creationMonster()
                prefab.setPosition(item.x, y)
                prefab.id = id
                this.node.addChild(prefab)
                $base.setTemplateItem(id, {
                    uuid,
                    monsterType: breed.toString()
                })
                this.actionDrop({
                    node: prefab, number: g.length, callback: () => {
                        this.addEvent(prefab)
                        i++
                        if (i === length) callback(true)
                    }
                })
            })
        })
    }

    blockDrop(callback = (boolean) => { }) { // 棋子落下 这个函数百分百执行回调
        const group = JSON.parse(JSON.stringify($base.getGroup().group))
        let length = 0
        group.forEach((g) => { // 先拿长度才能知道什么时候全部完成
            let counter = 0
            g.forEach((item) => {
                if (!item.info) {
                    counter += 1
                } else if (item.info && counter > 0) {
                    length++
                    item.counter = counter
                }
            })
        })
        if (length === 0) callback(false)
        let i = 0
        group.forEach((g) => {
            g.forEach((item) => {
                if (item.counter) {
                    const {
                        uuid
                    } = item.info
                    const node = <$base.Node>this.node.getChildByUuid(uuid)
                    this.actionDrop({
                        node, number: item.counter,
                        callback: () => {
                            i++
                            if (i === length) return callback(true)
                        }
                    })
                    let nextId = item.id - item.counter
                    node.id = nextId
                    $base.exchange(item.id, nextId)
                }
            })
        })
    }



    async onLoad() {
        this.monster.forEach((prefab: cc.Prefab) => {
            if (!prefab) return
            $base.initPool({ // 初始化对象池
                name: prefab.name,
                prefab
            })
        })
        await $base.getPos((data) => {
            this.maskList.push({
                x: data.x, y: data.y // 原点
            })
            return this.createdBlock(data)
        })
        this.createMask()
    }
}
